<?php
/**
 * Storefront_Model_Cart
 *
 * @category   Storefront
 * @package    Storefront_Model
 * @copyright  Copyright (c) 2008 Keith Pope (http://www.thepopeisdead.com)
 * @license    http://www.thepopeisdead.com/license.txt     New BSD License
 */
class Storefront_Model_Cart extends SF_Model_Abstract
    implements SeekableIterator, Countable, ArrayAccess
{
    /**
     * An array of cart items
     *
     * @var array 
     */
    protected $_items = array();
    
    /**
     * Total before shipping
     *
     * @var decimal
     */
    protected $_subTotal = 0;
    
    /**
     * Total with shipping
     *
     * @var decimal
     */
    protected $_total = 0;
    
    /**
     * The shipping cost
     *
     * @var decimal
     */
    protected $_shipping = 0;
    
    /**
     * ZNS for persistence, The session store
     *
     * @var Zend_Session_Namespace
     */
    protected $_sessionNamespace;
    
    /**
     * Called from __construct defined by model abstract
     */
    public function init()
    {
        
        $this->loadSession();
        
    }
    
    /**
     * Add or update ad item in the cart.
     *
     * @param Storefront_Resource_Product_Item $product
     * @param int $qty
     * @return Storefront_Resource_Cart_Item 
     */
    public function addItem(Storefront_Resource_Product_Item $product, $qty)
    {
        
        if(0 > $qty) {
            return false;
        }
        
        if(0 == $qty) {
            $this->removeItem($product);
            return false;
        }
        
        $item = new Storefront_Resource_Cart_Item($product, $qty);
        $this->_items[$item->productId] = $item;
        $this->persist();
        return $item;
        
    }
    
    /**
     * Remove an item from the cart
     *
     * @param Storefront_Resource_Product_Item $product 
     */
    public function removeItem($product)
    {
        
        if (is_int($product)) {
            unset ($this->_items[$product]);
        }
        
        if ($product instanceof Storefront_Resource_Product_Item) {
            unset ($this->_items[$product->productId]);
        }
        
        $this->persist();
        
    }
    
    /**
     * Setter for session namespace
     *
     * @param Zend_Session_Namespace $ns 
     */
    public function setSessions(Zend_Session_Namespace $ns) 
    {
        
        $this->_sessionNamespace = $ns;
        
    }
    
    /**
     * Getter for session namespace
     *
     * @return Zend_Session_Namespace
     */
    public function getSession()
    {
        
        if (null === $this->_sessionNamespace) {
            $this->setSessions(new Zend_Session_Namespace(__CLASS__));
        }
        return $this->_sessionNamespace;
        
    }
    
    /**
     * Persist (save) the data cart in the session
     */
    public function persist()
    {
        
        $this->getSession()->items = $this->_items;
        $this->getSession()->shipping = $this->getShippingCost();
        
    }
    
    /**
     * Load any persisted data
     */
    public function loadSession() {
        
        if(isset($this->getSession()->items)) {
            $this->_items = $this->getSession()->items;
        }
        
        if(isset($this->getSession()->shipping)) {
            $this->setShippingCost($this->getSession()->shipping);
        }
        
    }
    
    /**
     * Calculate the totals
     */
    public function calculteTotals()
    {
        
        $sub = 0;
        foreach ($this as $item) {
            $sub = $sub + $item->getLineCost();
        }
        
        $this->_subTotal = $sub;
        $this->_total = $this->_subTotal + (float) $this->_shipping;
        
    }
    
    /**
     * Set the shipping cost
     *
     * @param float $cost 
     */
    public function setShippingCost($cost)
    {
        
        $this->_shipping = $cost;
        $this->calculteTotals();
        $this->persist();
        
    }
    
    /**
     * Get shipping cost
     *
     * @return float
     */
    public function getShippingCost()
    {
        
        $this->calculteTotals();
        return $this->_shipping;
        
    }
    
    /**
     * Get the subtotal
     *
     * @return float 
     */
    public function getSubTotal()
    {
        
        $this->calculteTotals();
        return $this->_subTotal;
        
    }
    
    /**
     * Get the cart total
     *
     * @return float
     */
    public function getTotal()
    {
        
        $this->calculteTotals();
        return $this->_total;
        
    }

    /**
     * Does the given offset exists?
     *
     * @param string|int $key The key
     * @return boolean offset exists?
     */
    public function offsetExists($key) 
    {
        
        return isset($this->_items[$key]);
        
    }

    /**
     * Return the given offset
     *
     * @param string|int $key
     * @return mixed
     */
    public function offsetGet($key) 
    {
        
        return $this->_items[$key];
        
    }

    /**
     * set the value for the given offset.
     *
     * @param string|int $key
     * @param mixed $value 
     */
    public function offsetSet($key, $value) 
    {
        
        return $this->_items[$key] = $value;
        
    }

    /**
     * Unset the given element
     *
     * @param string|int $key 
     */
    public function offsetUnset($key) 
    {
        
        unset ($this->_items[$key]);
        
    }

    /**
     * Count the cart items
     *
     * @return int Row count
     */
    public function count() 
    {
        
        return count($this->_items);
        
    }

    /**
     * Returns the current row.
     *
     * @return mixed Current row.
     */
    public function current() 
    {
        
        return current($this->_items);
        
    }

    /**
     * Returns the current key
     *
     * @return mixed current key
     */
    public function key() 
    {
        
        return key($this->_items);
        
    }
    
    /**
     * Moves the internal pointer to the next item and
     * returns the new current item or false.
     *
     * @return mixed next item
     */
    public function next() 
    {
        
        return next($this->_items);
        
    }

    /**
     * Reset to the first item and return.
     *
     * @return mixed first item or false
     */
    public function rewind() 
    {
        
        return reset($this->_items);
        
    }
    
    /**
     * Check if there is a current element after calls to rewind() or next(). 
     *
     * @return boolean Valid item?
     */
    public function valid() 
    {
        
        return current($this->_items) !== false;
        
    }

    /**
     * Seek to the given index.
     *
     * @param int $index 
     */
    public function seek($index) 
    {
        
        $this->rewind();
        $position = 0;
        
        while($position < $index && $this->valid()) {
            $this->next();
            $position++;
        }
        
        if (!$this->valid()) {
            throw new SF_Model_Exception('Invalid seek position');
        }
       
    }



}

